const Db = require("./db");
const createSql = require("./Create_SQL");
const jwt = require('jsonwebtoken'); //生成、校验toke
const newGUID = require('./guid'); //生成GUID
const { uploadFile, deleteFile } = require('./cbFile'); //文件上传、删除
const { errorCode, successCode, config, Referer } = require("../config/config");
const { ACEencrypt, ACEdecrypt, MD5 } = require('./crypto'); //加密、解密
const request = require("request");
const os = require('os');
const $redis = require("./redisDb");

/**
 * 获取格式化时间
 * @param {String | Date} Format 格式化字符串 yyyy-MM-dd HH:mm:ss / 只有一个参数时为日期对象 
 * @param {Date | string} [date] 日期对象
 * @returns {String} 格式化后的时间
 * @example
 * getTime('yyyy-MM-dd HH:mm:ss', new Date()) // 2021-04-05 15:54:28
 * */
const getTime = (Format, date = new Date()) => {
    if (!Format) {
        Format = "yyyy-MM-dd HH:mm:ss"
    }
    let isDate = false;
    if (Format && new Date(Format) != 'Invalid Date') {
        date = new Date(Format);
        Format = 'yyyy-MM-dd HH:mm:ss';
    }
    if (typeof Format !== 'string') {
        throw new Error('Format must be a string')
    }
    if (date && typeof date == 'string') {
        try {
            date = new Date(date);
        } catch (error) {
            throw new Error('date must be a Date object')
        }
    }
    if (Object.prototype.toString.call(date) !== '[object Date]') {
        throw new Error('date must be a Date object')
    }
    let year = date.getFullYear();
    let month = date.getMonth() + 1;
    let day = date.getDate();
    let hour = date.getHours();
    let minute = date.getMinutes();
    let second = date.getSeconds();
    let millisecond = date.getMilliseconds();
    let o = {
        "M+": month,
        "d+": day,
        "H+": hour,
        "m+": minute,
        "s+": second,
        "S+": millisecond,
    };
    if (/(y+)/.test(Format)) {
        Format = Format.replace(
            RegExp.$1,
            (year + "").substr(4 - RegExp.$1.length)
        );
    }
    for (var k in o) {
        if (new RegExp("(" + k + ")").test(Format)) {
            Format = Format.replace(
                RegExp.$1,
                RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(String(o[k]).length)
            );
        }
    }
    return Format;
}

/**
 * 生成jwt秘钥
 * @param {string | object | Buffer } data 生成秘钥的数据
 * @param {string} secretOrPrivateKey 秘钥
 * @param {object} options 生成秘钥的配置
 * */
const GenerateSecretKey = (data, secretOrPrivateKey, options) => {
    if (!data) {
        throw new Error('data is not defined')
    }
    if (typeof data !== 'string' && typeof data !== 'object' && typeof data !== 'Buffer') {
        throw new Error('data must be a string or object or Buffer')
    }
    if (!secretOrPrivateKey) {
        throw new Error('secretOrPrivateKey is not defined')
    }
    if (!options) {
        options = {};
        options.expiresIn = 60 * 60 * 24 * 1; // 1天过期
    }
    return jwt.sign(data, secretOrPrivateKey, options);
}


/**
 * 解密 jwt
 * @param {string} token jwt
 * @param {string} secretOrPrivateKey 秘钥
 * @param {object} options 解密配置
 * @returns {Promise} 返回一个promise对象
 * */
const DecodeSecretKey = (token, secretOrPrivateKey, options) => {
    if (!token) {
        throw new Error('token is not defined')
    }
    if (!secretOrPrivateKey) {
        throw new Error('secretOrPrivateKey is not defined')
    }
    if (typeof token !== 'string') {
        throw new Error('token must be a string')
    }
    if (!options) {
        options = {};
    }
    return new Promise((resolve, reject) => {
        jwt.verify(token, secretOrPrivateKey, options, (err, decoded) => {
            if (err) {
                reject(err)
            } else {
                resolve(decoded)
            }
        })
    })
}

/**生成 GUID*/
const getGuid = () => {
    let guid = newGUID.guid.newGUID();
    return guid;
}

/**
 * 获取客户端IP
 * @param {Object} req - 请求体
 */
function getClientIP(req) {
    let ip = req.headers['X-Real-IP'] || req.headers['x-forwarded-for']  // 判断是否有反向代理 IP req ||req.connection.remoteAddress || // 判断 connection 的远程 IP
        || req.socket.remoteAddress || // 判断后端的 socket 的 IP
        req.connection.socket.remoteAddress || req.headers['origin'];
    let myIp = ip.replace(/::ffff:/g, "");
    myIp = myIp.replace('https://', '').replace("http://", '')
    if (myIp.indexOf(",") > -1) {
        myIp = myIp.split(",")[0];
    }
    return myIp;
};

/**
 * 获取本机ip
 * */
function getIPAdress() {
    var interfaces = os.networkInterfaces();
    for (var devName in interfaces) {
        var iface = interfaces[devName];
        for (var i = 0; i < iface.length; i++) {
            var alias = iface[i];
            if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
                return alias.address;
            }
        }
    }
}


/**
 * 后台判断是否登录
 * @param {Object} req - 请求体
 * @param {Object} res - 响应体
 * @param {Boolean} isSend - 是否发送响应
 * */
const isLogin = (req, res, isSend = true) => {
    if (config.development) {
        return new Promise((resolve, reject) => {
            resolve({
                ...successCode[200],
                data: {
                    ID: "c581a60c-f134-8be5-fa55-9adad7183019",
                    username: "admin"
                }
            })
        })
    }
    if (!req) {
        throw new Error('req is not defined')
    }
    if (!res && 0) {
        throw new Error('res is not defined')
    }
    // 获取cookie token
    let token = req.cookies.token;
    return new Promise((resolve, reject) => {
        if (!token) {
            if (isSend) loginSend(res, 401, errorCode[10019]);
            reject({
                ...errorCode[10019],
            });
            return;
        }
        DecodeSecretKey(token, config.secretOrPrivateKey).then(async (decoded) => {
            let sql = createSql.select().from("user_admin").where(`ID = '${decoded.ID}'`).end();
            let data = await Db.query(sql, res);
            if (data.length == 0) {
                if (isSend) loginSend(res, 500, errorCode[10012]);
                reject({
                    ...errorCode[10012],
                });
                return;
            }
            data = data[0];
            if (data.token != token) {
                if (isSend) loginSend(res, 401, errorCode[10019]);
                reject({
                    ...errorCode[10019],
                });
                return;
            }
            if (data.is_disable != '0') {
                if (isSend) loginSend(res, 500, { ...errorCode[10010], message: "该用户已被禁用" });
                reject({
                    ...errorCode[10010],
                    message: "该用户已被禁用"
                });
                return;
            }
            resolve({
                ...successCode[200],
                data: {
                    ID: data.ID,
                    username: data.user_name,
                },
                userInfo: data
            });
        }).catch((err) => {
            reject({
                ...errorCode[10019],
            })
            if (isSend) {
                loginSend(res, 401, errorCode[10019]);
                return;
            }
        })
    })
}




/**登录发送响应体*/
const loginSend = (res, status, data) => {
    res.status(status);
    return res.json(data);
}


/**
 * 用户是否登录
 * @param {Object} req - 请求体
 * @param {Object} res - 响应体
 * @param {Boolean} isSend - 是否发送响应 默认true
 * */
const isUserLogin = (req, res, isSend = true) => {
    if (!req) {
        throw new Error('req is not defined')
    }
    if (!res) {
        throw new Error('res is not defined')
    }
    // 获取token 因为有可能是小程序 所以要从多个地方获取
    let token = req.cookies.token || req.headers.token || req.query.token || req.body.token || req.headers.cookie;
    return new Promise((resolve, reject) => {
        if (!token) {
            if (isSend) loginSend(res, 401, errorCode[10019]);
            reject({
                ...errorCode[10019],
            });
            return;
        }
        DecodeSecretKey(token, config.secretOrPrivateKey).then(async (decoded) => {
            let sql = createSql.select().from("web_userinfo").where(`ID = '${decoded.ID}'`).end();
            let data = await Db.query(sql, res);
            if (data.length == 0) {
                if (isSend) loginSend(res, 500, errorCode[10012]);
                reject({
                    ...errorCode[10012],
                });
                return;
            }
            data = data[0];
            if (data.is_stop != '0') {
                if (isSend) loginSend(res, 500, { ...errorCode[10010], message: "该用户已被禁用" });
                reject({
                    ...errorCode[10010],
                    message: "该用户已被禁用"
                });
                return;
            }
            resolve({
                ...successCode[200],
                data: decoded
            });
        }).catch((err) => {
            reject({
                ...errorCode[10019],
            })
            if (isSend) {
                loginSend(res, 401, errorCode[10019]);
                return;
            }
        })
    });
}

/**
 * 获取 getAccessToken
 * @param {Function} [success] 成功返回
 * @param {Function} [fail] 失败返回
 * @returns Promise acctoken
 * */
const getAccessToken = async (option) => {
    let options = {
        success: () => { },
        fail: () => { }
    }
    if (option) Object.assign(options, option);
    return new Promise(async (resolve, reject) => {
        let access_token = await $redis.getData("access_token");
        if (access_token) {
            options.success(access_token);
            resolve(access_token);
            return;
        }
        let url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${config.appid}&secret=${config.AppSecret}`;
        request(url, {
            json: true,
            headers: {
                'Content-Type': 'application/json'
            },
            method: "GET"
        }, (error, response, body) => {
            if (error) {
                options.fail(error);
                reject(error);
                return;
            }
            if (body.access_token) {
                $redis.setExpire("access_token", body.access_token, 7200);
                options.success(body.access_token);
                resolve(body.access_token);
            } else {
                options.fail(body);
                reject(body);
            }
        })
    });

}

/**
 * 检测数据是否符合要求
 * @param {object} data 原始数据
 * @param {Object} checkData 校验数据
 * @param {Boolean} isSQL 是否校验数据
 * @returns {object} {code : 200 , data : data} || {code : 10011 | 10003 , message : message}
 */
const checkData = (data, checkData, isSQL = false) => {
    if (Object.prototype.toString.call(data) !== '[object Object]') {
        return {
            ...errorCode[10011],
            message: "data is not Object"
        }
    }
    if (JSON.stringify(data) == "{}") {
        return {
            ...successCode[200],
            data: data
        }
    }
    if (!checkData) {
        return {
            ...errorCode[10003],
            message: "checkData is not defined"
        }
    }
    let isCheck = true;
    let sendData = {};
    for (let key in checkData) {
        if (isSQL) {
            isCheck = detectSQL(checkData[key]);
        }
        if (checkData[key].required && (data[key] == undefined || data[key] == null || data[key] == "")) {
            isCheck = false;
            return {
                ...errorCode[10003],
                message: checkData[key].message || `${key} is required`
            }
        }

        if (checkData[key].type == 'Number' && !isNaN(Number(data[key]))) {
            data[key] = Number(data[key]);
        }
        if (checkData[key].type == 'Boolean') {
            if (data[key] == 'true' || data[key] == true || data[key] == 1 || data[key] == '1') {
                data[key] = true;
            } else if (data[key] == 'false' || data[key] == false || data[key] == 0 || data[key] == '0') {
                data[key] = false;
            } else if (data[key] == undefined || data[key] == null || data[key] == "") {
                data[key] = false;
            } else {
                isCheck = false;
                return {
                    ...errorCode[10003],
                    message: `${key} is not Boolean`
                }
            }
        }
        if (checkData[key].type == 'JSONstring') {
            try {
                JSON.parse(data[key]);
                checkData[key].type = 'String';
            } catch (error) {
                if (data[key] == undefined || data[key] == null || data[key] == "") {
                    data[key] = "{}";
                    checkData[key].type = 'String';
                } else {
                    isCheck = false;
                    return {
                        ...errorCode[10003],
                        message: `${key} is not JSONstring`
                    }
                }
            }
        }
        if (checkData[key].required && checkData[key].type && Object.prototype.toString.call(data[key]) !== `[object ${checkData[key].type}]`) {
            isCheck = false;
            return {
                ...errorCode[10003],
                message: `${key} is not ${checkData[key].type}`
            }
        }
        if (data[key] !== undefined) {
            sendData[key] = data[key];
        }
    }
    return {
        ...successCode[200],
        data: sendData
    };
}

/**
 * 防止sql注入
 * @param {string | Object} str
 * */
const detectSQL = (str) => {
    if (!str) {
        throw Error("需要检验的字符串不能为空");
    }
    if (typeof str !== 'string') {
        try {
            str = JSON.stringify(str);
        } catch {
            throw Error("需要检验的字符串不是字符串/对象");
        }
    }
    str = str.toUpperCase();
    const keywords = ['SELECT', 'UPDATE', 'DELETE', 'INSERT', 'CREATE', 'DROP', 'TRUNCATE', 'ALTER', 'RENAME', 'REPLACE', 'FROM', 'WHERE', 'AND', 'OR', 'ORDER BY', 'GROUP BY', 'HAVING', 'LIMIT', 'OFFSET', 'UNION', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'OUTER JOIN', 'FULL JOIN', 'CROSS JOIN', 'NATURAL JOIN', 'ON', 'AS', 'SET', 'VALUES', 'INTO', 'TABLE', 'DATABASE', 'INDEX', 'CONSTRAINT', 'PRIMARY KEY', 'FOREIGN KEY', 'UNIQUE', 'CHECK', 'DEFAULT', 'AUTO_INCREMENT', 'NOT NULL', 'NULL', 'DESC', 'ASC', 'IS NULL', 'IS NOT NULL', 'LIKE', 'BETWEEN', 'IN', 'EXISTS', 'ALL', 'ANY', 'SOME', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'WHILE', 'DO', 'FOR', 'IF', 'ELSE', 'ELSEIF', 'BEGIN', 'END', 'DECLARE', 'RETURN', 'FUNCTION', 'PROCEDURE', 'TRIGGER', 'VIEW', 'CURSOR', 'OPEN', 'CLOSE', 'FETCH', 'EXIT', 'LOOP', 'GOTO', 'CALL', 'USING', 'OUT', 'INOUT', 'IN', 'AS', 'BEGIN', 'END', 'IF', 'ELSE', 'ELSEIF', 'THEN', 'WHILE', 'DO', 'FOR', 'RETURN', 'REPEAT', 'UNTIL', 'LEAVE', 'ITERATE', 'RECURSIVE', 'REPLACE', 'HANDLER',
        'CONDITION', 'SIGNAL', 'RESIGNAL', 'GET', 'SET', 'SHOW', 'LOCK', 'AND', 'OR', 'XOR', 'NOT', 'BINARY', 'DIV', 'MOD', 'REGEXP', 'RLIKE', 'SOUNDS LIKE', 'LIKE', 'IN', 'BETWEEN', 'IS', 'IS NOT', 'NULL', 'TRUE', 'FALSE', 'UNKNOWN', 'AND', 'OR', 'XOR', 'NOT', 'BETWEEN', 'IN', 'LIKE', 'REGEXP', 'IS', 'IS NOT', 'NULL', 'TRUE', 'FALSE', 'UNKNOWN', 'DIV', 'MOD', 'AND', 'OR', 'XOR', 'NOT', 'BETWEEN', 'IN', 'LIKE', 'REGEXP', 'IS', 'IS NOT', 'NULL', 'TRUE', 'FALSE', 'UNKNOWN', 'DIV', 'MOD', 'AND', 'OR', 'XOR', 'NOT', 'BETWEEN', 'IN', 'LIKE', 'REGEXP', 'IS', 'IS NOT', 'NULL', 'TRUE', 'FALSE', 'UNKNOWN', 'DIV', 'MOD', 'AND', 'OR', 'XOR', 'NOT', 'BETWEEN', 'IN', 'LIKE', 'REGEXP', 'IS', 'IS NOT', 'NULL', 'TRUE', 'FALSE', 'UNKNOWN', 'DIV', 'MOD', 'AND', 'OR', 'XOR', 'NOT', 'BETWEEN', 'IN', 'LIKE', 'REGEXP', 'IS', 'IS NOT', 'NULL', 'TRUE', 'FALSE', 'UNKNOWN', 'DIV', 'MOD', 'AND', 'OR', 'XOR', 'NOT', 'BETWEEN', 'IN', 'LIKE', 'REGEXP', 'IS', 'IS NOT', 'NULL', 'TRUE', 'FALSE', 'UNKNOWN', 'DIV', 'MOD',
        'COUNT', '*', '%', '+', '>', '<', '|', '=', 'SCRIPT', 'MID', 'TRUNCATE', 'CHAR', 'ETC', 'STYLE', 'EXPRESSION'];
    let pass = true;
    keywords.forEach(item => {
        if (str.indexOf(item) != -1) {
            pass = false;
        }
    })
    return pass
}


/**
 * 删除数组对象中指定的元素
 * @param {Array} arr 原数组
 * @param {string | Array} keys 键名
 * @return {Array}
 * */
const deleteArrayObject = (arr, keys) => {
    arr.forEach(item => {
        if (typeof keys == 'string') {
            if (key in item) {
                delete item[key];
            }
        } else if (typeof keys == 'object') {
            keys.forEach(key => {
                if (key in item) {
                    delete item[key];
                }
            })
        }
    })
    return arr;
}


/**
 * 统一格式数组对象的时间
 * @param {Array} arr 原数组
 * @param {string | Array} keys 时间字段名
 * @return {Array}
 * */
const formatDateByArray = (arr, keys) => {
    arr.forEach(item => {
        if (typeof keys == 'string') {
            if (keys in item) {
                item[keys] = item[keys] ? getTime(item[keys]) : "";
            }
        } else if (typeof keys == 'object') {
            keys.forEach(key => {
                if (key in item) {
                    item[key] = item[key] ? getTime(item[key]) : "";
                }
            })
        }
    })
    return arr;
}

/**
 * 数组对象的数字转boolean
 * @param {Array} arr 原数组
 * @param {string | Array} keys 数字字段名
 * @return {Array}
 * */
const formatNumberToBoolean = (arr, keys) => {
    arr.forEach(item => {
        if (typeof keys == 'string') {
            if (keys in item) {
                item[keys] = item[keys] == 1 ? true : false;
            }
        } else if (typeof keys == 'object') {
            keys.forEach(key => {
                if (key in item) {
                    item[key] = item[key] == 1 ? true : false;
                }
            })
        }
    })
    return arr;
}

/**中间件：校验 Referer*/
const checkReferer = (req, res, next) => {
    let referer = req.headers.referer || '';
    // 获取 wss 的 referer
    // 当为 ws/wss 请求时，referer 为空，需要从 host 中获取
    // headers 中对象的key有 WebSocket 就表示有 ws/wss 请求
    let hasWebSocket = Object.keys(req.headers).find(key => key.toLowerCase() === 'websocket');
    if (hasWebSocket) {
        referer = 'ws://' + req.headers.host;
    }
    // 检查 referer 是否为空
    if (!referer) {
        return res.status(403).json(errorCode[403]);
    }
    let refererUrl;
    try {
        refererUrl = new URL(referer);
    } catch (error) {
        // 如果解析失败，可能是因为它不是一个有效的 URL（比如只是一个 IP 地址）
        // 在这种情况下，我们直接比较字符串
        if (!/^\d+\.\d+\.\d+\.\d+(\:\d+)?(\/.*)?$/.test(referer)) {
            // 如果它也不是一个有效的 IP 地址格式，则拒绝请求
            return res.status(403).json(errorCode[403]);
        }
    }
    const refererHostnameOrIp = refererUrl ? refererUrl.hostname : referer.split('/')[0]; // 获取主机名或 IP 地址部分
    // 检查 referer 主机名或 IP 地址是否匹配允许的 Referer 正则表达式数组中的任何一个  
    const isAllowed = Referer.some(regex => regex.test(refererHostnameOrIp));
    if (!isAllowed) {
        return res.status(403).json(errorCode[403]);
    }
    next(); // 如果 Referer 是允许的，则继续处理请求
}

/**获取ip地址的位置信息*/
const getIPByLocation = (ip) => {
    return new Promise((resolve, reject) => {
        // https://opendata.baidu.com/api.php?query=[${ip}]&co=&resource_id=6006&oe=utf8
        request(`https://api.vore.top/api/IPdata?ip=${ip}`, function (error, response, body) {
            if (!error && response.statusCode == 200) {
                try {
                    let data = JSON.parse(body);
                    resolve(data);
                } catch (error) {
                    resolve({});
                }
            }
        })
    });
}

// getIPByLocation('116.132.136.89').then(data => {
//     console.log("返回的数据 ==>" ,data);
// })

module.exports = {
    getTime: getTime,
    GenerateSecretKey: GenerateSecretKey,
    DecodeSecretKey: DecodeSecretKey,
    getUuid: getGuid,
    uploadFile: uploadFile,
    deleteFile: deleteFile,
    getClientIP: getClientIP,
    isLogin: isLogin,
    ACEencrypt: ACEencrypt,
    ACEdecrypt: ACEdecrypt,
    MD5: MD5,
    getAccessToken: getAccessToken,
    checkData: checkData,
    getIPAdress: getIPAdress,
    formatDate: getTime,
    detectSQL: detectSQL,
    isUserLogin: isUserLogin,
    deleteArrayObject: deleteArrayObject,
    formatDateByArray: formatDateByArray,
    formatNumberToBoolean: formatNumberToBoolean,
    checkReferer: checkReferer,
    getIPByLocation : getIPByLocation
}; 